package com.bestcem.xm.user.service.impl;

import cn.hutool.core.lang.ObjectId;
import com.alibaba.fastjson.JSONObject;
import com.bestcem.xm.common.core.constant.AppRequestErrorCode;
import com.bestcem.xm.common.core.constant.AppResourceErrorCode;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.enums.AuthErrorCodeEnum;
import com.bestcem.xm.common.core.enums.ReqErrorCodeEnum;
import com.bestcem.xm.common.core.uitls.DateUtil;
import com.bestcem.xm.component.security.dto.TokenDTO;
import com.bestcem.xm.user.constant.SmsConstant;
import com.bestcem.xm.user.constant.UserCacheKeyConstant;
import com.bestcem.xm.user.dao.UserMfaSettingDao;
import com.bestcem.xm.user.enums.*;
import com.bestcem.xm.user.mq.message.user.SmsMessage;
import com.bestcem.xm.user.service.*;
import com.bestcem.xm.user.service.dto.user.*;
import com.bestcem.xm.user.util.UserAsyncUtil;
import com.bestcem.xm.user.util.business.DataBaseUtil;
import com.bestcem.xm.user.util.business.StringFormatUtil;
import com.bestcem.xm.user.util.business.UserCacheUtil;
import com.bestcem.xm.user.util.convert.UserMfaSettingConvert;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 用户多因素认证相关服务
 *
 * @author panyun <yun.pan@idiaoyan.com>
 * @version v1.0
 * @date 2022/6/9 17:12
 */
@Slf4j
@Service
public class UserMfaSettingServiceImpl implements UserMfaSettingService {

    /**
     * 重置密码设置的token有效时长
     */
    private static final long RESET_PASSWORD_TOKEN_TIMEOUT = 2 * 60 * 60L;
    /**
     * 重置密码设置的token有效时长
     */
    private static final long USER_SET_SKIP_MFA_TIMEOUT = 15 * 24 * 60 * 60L;

    @Resource
    private UserMfaSettingDao userMfaSettingDao;

    @Resource
    private UserMfaSettingConvert userMfaSettingConvert;

    @Resource
    private OrganizationService organizationService;

    @Resource
    private UserService userService;

    @Resource
    private UserMfaRoleService userMfaRoleService;

    @Resource
    private UserUserRoleService userUserRoleService;

    @Resource
    private MailService mailService;

    @Resource
    private UserSmsService userSmsService;

    //@Resource
    //private UserMessageSender userMessageSender;

    @Lazy
    @Resource
    private UserAsyncUtil userAsyncUtil;

    @Resource
    private UserCacheUtil userCacheUtil;

    @Override
    public ServiceResult<JudgeNeedMfaLoginDTO> judgeNeedMfaLogin(String orgId, UserDTO user) {
        ServiceResult<UserMfaSettingDTO> mfaSettingByOrgId = getMfaSettingByOrgId(orgId);
        UserMfaSettingDTO orgMfa = mfaSettingByOrgId.getData();

        // 判断是否需要mfa登录
        if (Objects.nonNull(orgMfa) && orgMfa.getOpenMfa().equals(UserEnum.USER_YES.getFlag())
                && Boolean.FALSE.equals(checkUserSkipMfa(user.getUserId()))) {
            // 获取用户关联的roleId列表
            ServiceResult<List<String>> userRoleIdResult = userUserRoleService.listRoleIdsByUserId(user.getUserId());
            List<String> userRoleIds = userRoleIdResult.getData();

            // 获取该租户下需要mfa登录的roleId列表
            UserMfaRoleDTO userMfaRole = new UserMfaRoleDTO();
            userMfaRole.setOrgId(orgId);
            ServiceResult<List<UserMfaRoleDTO>> orgNeedMfaRoles = userMfaRoleService.listMfaRoleByCondition(userMfaRole);
            List<String> orgNeedMfaRoleIds = orgNeedMfaRoles.getData().stream().map(UserMfaRoleDTO::getRoleId).collect(Collectors.toList());

            // 取userRoleIds、orgNeedMfaRoleIds交集,不为空则需要mfa登录
            List<String> userNeedMfaRoleIds = userRoleIds.stream().filter(orgNeedMfaRoleIds::contains).collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(userNeedMfaRoleIds)) {
                JudgeNeedMfaLoginDTO dto = new JudgeNeedMfaLoginDTO();
                dto.setUserId(user.getUserId())
                        .setAuthMethod(orgMfa.getAuthMethod())
                        .setMobile(user.getPhone())
                        .setEmail(user.getEmail())
                        .setAllowSkip(orgMfa.getAllowSkip().equals(UserEnum.USER_YES.getFlag()) ? Boolean.TRUE : Boolean.FALSE);
                return ServiceResult.fail(dto, String.format("user[id=%s] need mfa.", user.getUserId()), String.valueOf(AuthErrorCodeEnum.USER_NEED_MFA_ERROR.getCode()));
            }
        }
        return ServiceResult.success();
    }

    @Override
    public ServiceResult<UserMfaSettingDTO> getMfaSettingByOrgId(String orgId) {
        if (!ObjectId.isValid(orgId)) {
            ServiceResult.fail(ReqErrorCodeEnum.PARAM_FORMAT, "orgId格式不正确");
        }
        return ServiceResult.success(userMfaSettingConvert.do2Dto(userMfaSettingDao.getMfaSettingByOrgId(orgId)));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ServiceResult<String> updateMfaSetting(TokenDTO currentToken, Boolean openMfa, Integer authMethod, Boolean allowSkip, List<String> roleList) {
        String orgId = currentToken.getOrgId();
        String userId = currentToken.getUserId();
        // 根据orgId查询UserMfaSettingDTO
        ServiceResult<UserMfaSettingDTO> mfaSettingByOrgId = getMfaSettingByOrgId(orgId);
        if (!mfaSettingByOrgId.isSuccess() || Objects.isNull(mfaSettingByOrgId.getData())) {
            return ServiceResult.fail(AppResourceErrorCode.NOT_FOUND.toString(), String.format("MFASetting[orgId=%s] not found.", orgId));
        }
        UserMfaSettingDTO userMfaSettingDTO = mfaSettingByOrgId.getData();

        //开启安全验证
        if (Boolean.TRUE.equals(openMfa)) {
            ServiceResult<Integer> mobileUnBind = checkOrgContactAllBind(orgId, "mobile");
            ServiceResult<Integer> emailUnBind = checkOrgContactAllBind(orgId, "email");
            if (authMethod.equals(MfaSettingAuthMethodEnum.MFASETTING_AUTH_METHOD_SMS.getType()) && mobileUnBind.getData() == 0
                    || authMethod.equals(MfaSettingAuthMethodEnum.MFASETTING_AUTH_METHOD_EMAIL.getType()) && emailUnBind.getData() == 0) {
                //更新UserMfaSetting
                UserMfaSettingDTO dto = new UserMfaSettingDTO();
                dto.setId(userMfaSettingDTO.getId())
                        .setOpenMfa(UserEnum.USER_YES.getFlag())
                        .setAuthMethod(authMethod)
                        .setAllowSkip(allowSkip.equals(Boolean.TRUE) ? UserEnum.USER_YES.getFlag() : UserEnum.USER_NO.getFlag());
                updateById(dto);

                // 更新mfa_role表
                UserMfaRoleDTO userMfaRole = new UserMfaRoleDTO();
                userMfaRole.setOrgId(orgId);
                ServiceResult<List<UserMfaRoleDTO>> listServiceResult = userMfaRoleService.listMfaRoleByCondition(userMfaRole);
                List<String> oldRoleIds = listServiceResult.getData().stream().map(UserMfaRoleDTO::getRoleId).collect(Collectors.toList());
                List<String> deleteRoleIds = oldRoleIds.stream().filter(t -> !roleList.contains(t)).collect(Collectors.toList());
                List<String> createRoleIds = roleList.stream().filter(t -> !oldRoleIds.contains(t)).collect(Collectors.toList());

                if (CollectionUtils.isNotEmpty(deleteRoleIds)) {
                    userMfaRoleService.deleteByRoleIds(orgId, deleteRoleIds);
                }
                for (String roleId : createRoleIds) {
                    userMfaRole.setId(DataBaseUtil.generateId())
                            .setOrgId(orgId)
                            .setRoleId(roleId)
                            .setCreatorId(userId);
                    userMfaRoleService.insertSelective(userMfaRole);
                }

                if (allowSkip.equals(Boolean.FALSE)) {
                    UserDTO userDTO = new UserDTO();
                    userDTO.setOrgId(orgId)
                            .setStatus(UserStatusEnum.ACTIVATED.getStatus());
                    ServiceResult<List<UserDTO>> userResult = userService.listByCondition(userDTO);
                    List<String> userIdList = userResult.getData().stream().map(UserDTO::getUserId).collect(Collectors.toList());
                    deleteSkipMfaFlag(userIdList);
                }
                return ServiceResult.success(userMfaSettingDTO.getId());
            } else {
                return ServiceResult.fail(AppResourceErrorCode.FORBIDDEN.toString(), "租户联系人并非完全绑定.");
            }
        } else {
            //关闭安全验证
            UserMfaSettingDTO dto = new UserMfaSettingDTO();
            dto.setId(userMfaSettingDTO.getId());
            if (Objects.nonNull(openMfa)) {
                dto.setOpenMfa(BooleanStatusEnum.NO.getStatus());
                updateById(dto);
            }
            return ServiceResult.success(userMfaSettingDTO.getId());
        }
    }

    @Override
    public ServiceResult<Integer> checkOrgContactAllBind(String orgId, String method) {
        if (!method.equals("mobile") && !method.equals("email")) {
            ServiceResult.fail(AppRequestErrorCode.PARAM_RANGE, "method must be mobile or email.");
        }

        // 查询redis
        String key = userCacheUtil.buildKey(UserCacheKeyConstant.USER_MFA_CHECK_BIND, method, orgId);
        String flag = userCacheUtil.get(key);
        if (Objects.nonNull(flag)) {
            return ServiceResult.success(Integer.valueOf(flag));
        }

        ServiceResult<OrganizationDTO> orgResult = organizationService.selectByPrimaryKey(orgId);
        if (!orgResult.isSuccess() || Objects.isNull(orgResult.getData())) {
            return ServiceResult.fail(AppResourceErrorCode.NOT_FOUND, "org not found.");
        }

        // 查询数据库
        List<UserDTO> userDTOS = listUserByCondition(orgId);
        int unbindNum = 0;
        for (UserDTO dto : userDTOS) {
            if (method.equals("email") && StringUtils.isBlank(dto.getEmail())) {
                unbindNum += 1;
            }
            if (method.equals("mobile") && StringUtils.isBlank(dto.getPhone())) {
                unbindNum += 1;
            }
        }

        // 在redis中保存
        userCacheUtil.setEx(key, String.valueOf(unbindNum), 3, TimeUnit.SECONDS);
        return ServiceResult.success(unbindNum);
    }

    @Override
    public ServiceResult<List<ExportMfaUnbindDTO>> genNotBindContactAccount(OrganizationDTO org, String ttype) {
        // 查询租户的用户列表
        List<UserDTO> userDTOS = listUserByCondition(org.getOrgId());
        ArrayList<ExportMfaUnbindDTO> data = new ArrayList<>(userDTOS.size());

        // 统计未绑定的账号数据
        for (UserDTO dto : userDTOS) {
            if (ttype.equals("email") && StringUtils.isBlank(dto.getEmail())) {
                ExportMfaUnbindDTO mfaUnbindDTO = new ExportMfaUnbindDTO();
                mfaUnbindDTO.setUserName(dto.getUserName());
                mfaUnbindDTO.setPhone(dto.getPhone());
                data.add(mfaUnbindDTO);
            }
            if (ttype.equals("mobile") && StringUtils.isBlank(dto.getPhone())) {
                ExportMfaUnbindDTO mfaUnbindDTO = new ExportMfaUnbindDTO();
                mfaUnbindDTO.setUserName(dto.getUserName());
                mfaUnbindDTO.setEmail(dto.getEmail());
                data.add(mfaUnbindDTO);
            }
        }
        return ServiceResult.success(data);
    }

    @Override
    public void sendMfaVcode(UserDTO user, Integer authMethod) {
        String vcode = String.format("%06d", RandomUtils.nextInt(0, 1000000));
        SmsMessage smsMessage = new SmsMessage();

        // 邮箱验证
        if (authMethod.equals(MfaSettingAuthMethodEnum.MFASETTING_AUTH_METHOD_EMAIL.getType())) {
            String key = userCacheUtil.buildKey(UserCacheKeyConstant.USER_MFA_EMAIL_PREFIX, user.getEmail());
            userCacheUtil.setEx(key, vcode.toLowerCase(), 10L * 60, TimeUnit.SECONDS);
            String content = "验证码" + vcode + "，若非本人操作请忽略";

            String fileName = EmailTemplateEnum.VCODE_TEMPLATE.getFileName();
            HashMap<String, Object> map = new HashMap<>();
            map.put("vcode", vcode);
            userAsyncUtil.emailSendAndNotifyOm(new String[]{user.getEmail()}, null, "倍市得登录安全验证", fileName, map);

            // MQ消息
            smsMessage.setContent(content)
                    .setName("安全验证")
                    .setOrgId(user.getOrgId())
                    .setReceiver(user.getEmail())
                    .setSender("")
                    .setType(authMethod)
                    .setStatus(NotifyStatusEnum.IS_SEND.getType())
                    .setSendTime(new SimpleDateFormat(DateUtil.NORM_DATETIME_PATTERN).format(DataBaseUtil.getDate()))
                    .setParams((String) new JSONObject().put("vcode", vcode));
        }

        // 短信验证
        if (authMethod.equals(MfaSettingAuthMethodEnum.MFASETTING_AUTH_METHOD_SMS.getType())) {
            String key = userCacheUtil.buildKey(UserCacheKeyConstant.USER_MFA_SMS_PREFIX, user.getPhone());
            userCacheUtil.setEx(key, vcode, 10L * 60, TimeUnit.SECONDS);
            HashMap<String, Object> map = new HashMap<>();
            map.put("vcode", vcode);
            String content = StringFormatUtil.format(SmsConstant.SMS_VCODE_TEMPLATE, map);
            Boolean sendSms = userSmsService.sendSms(user.getPhone(), content);

            // MQ消息
            smsMessage.setContent(content)
                    .setName("安全验证")
                    .setOrgId(user.getOrgId())
                    .setReceiver(user.getPhone())
                    .setSender("")
                    .setType(authMethod)
                    .setStatus(Boolean.TRUE.equals(sendSms) ? NotifyStatusEnum.IS_SEND.getType() : NotifyStatusEnum.SEND_FAILED.getType())
                    .setSendTime(new SimpleDateFormat(DateUtil.NORM_DATETIME_PATTERN).format(DataBaseUtil.getDate()))
                    .setParams((String) new JSONObject().put("vcode", vcode))
                    .setSmsChannel(NotifySmsChannelEnum.YIMEI.getType());
        }

        // 发送MQ
        //userMessageSender.publishOmSmsSyc(smsMessage);
    }

    @Override
    public void userSetSkipMfa(String userId) {
        long timeout = DataBaseUtil.getTodayRemainSecondNum() + USER_SET_SKIP_MFA_TIMEOUT;
        String key = userCacheUtil.buildKey(UserCacheKeyConstant.USER_MFA_SKIP_PREFIX, userId);
        userCacheUtil.setEx(key, "1", timeout, TimeUnit.SECONDS);
    }

    @Override
    public Boolean checkUserSkipMfa(String userId) {
        String key = userCacheUtil.buildKey(UserCacheKeyConstant.USER_MFA_SKIP_PREFIX, userId);
        String skip = userCacheUtil.get(key);
        if (StringUtils.isNotBlank(skip)) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    @Override
    public void deleteSkipMfaFlag(List<String> userIdList) {
        List<String> keys = userIdList.stream().map(userId ->
                userId = userCacheUtil.buildKey(UserCacheKeyConstant.USER_MFA_SKIP_PREFIX, userId)
        ).collect(Collectors.toList());
        userCacheUtil.delete(keys);
    }

    @Override
    public ServiceResult<Integer> updateById(UserMfaSettingDTO dto) {
        dto.setUpdateTime(new Date());
        return ServiceResult.success(userMfaSettingDao.updateByPrimaryKeySelective(userMfaSettingConvert.dto2Do(dto)));
    }

    @Override
    public ServiceResult<Integer> insertSelective(UserMfaSettingDTO dto) {
        return ServiceResult.success(userMfaSettingDao.insertSelective(userMfaSettingConvert.dto2Do(dto)));
    }

    private List<UserDTO> listUserByCondition(String orgId) {
        UserDTO userDTO = new UserDTO();
        userDTO.setOrgId(orgId)
                .setBiShareFlag(UserEnum.USER_NO.getFlag());
        return userService.listByCondition(userDTO).getData();
    }
}
